/**
 *
 * Manager usage of vm mapped to /dev/mem 0x1000000~0x1dfffff
 * When create a MemPool, it's pool_ should be alloc use this class.
 * It will first alloc firstly try to alloc from memory map to /dev/mem.
 * If there is not enough memory in /dev/mem 0x1000000~0x1dfffff, it
 * will try to call calloc() func of stdlib to alloc memory.
 */

#ifndef UTILS_MEM_ALLOCATOR_H_
#define UTILS_MEM_ALLOCATOR_H_

#include <unistd.h>

#include <cstring>
#include <list>
#include <memory>
#include <mutex>
#include <unordered_map>
#include <utility>
#include <vector>
#include "fake_log.h"

namespace MemTest {
class MemAllocDelegate;
class MemRange;

//=====================================================================
// MemMapMemAllocator
// Use MemMap to map /dev/mem 0x1000000-0x1dffefff into virtual memory.
// Use a linked table to store ranges in this mapping space. A range
// may be free or in-use.
//=====================================================================
const char *const kMemMapMemAllocatorName = "MemMapMemAllocator";
const char *const kDefaultMemAllocatorName = "DefaultMemAllocator";
class MemMapMemAllocator {
 public:
  int8_t *mem_calloc(uint32_t block_num, uint32_t block_size);
  void mem_free(const int8_t *const in_addr);
  bool addr_is_valid(const int8_t *const in_addr) const;
  bool is_valid() const;
  MemMapMemAllocator();
  MemMapMemAllocator(uint32_t in_base_addr, uint32_t in_high_addr);
  ~MemMapMemAllocator();

 private:
  std::shared_ptr<MemRange> search_first_applicable_range_(uint32_t size_need) const;
  int8_t *alloc_in_free_range_(const std::shared_ptr<MemRange> range_ptr, uint32_t size_need);
  std::shared_ptr<MemRange> find_target_range_(const int8_t *const in_addr) const;
  static void merge_contiguous_range_(const std::shared_ptr<MemRange> first_range_ptr,
                                      const std::shared_ptr<MemRange> second_range_ptr);

 private:
  std::shared_ptr<MemRange> range_list_ptr_;
  static std::mutex mmap_mem_allocator_mutex_;
  int32_t mem_fd_;
  int8_t *map_base_addr_;
  uint32_t page_size_;
  uint32_t base_addr_;
  uint32_t high_addr_;
};

struct MemRange {
  int8_t *range_offset;
  int8_t *range_end;
  uint32_t range_size;
  bool is_dirty;
  std::shared_ptr<MemRange> next_range;
  std::weak_ptr<MemRange> pre_range;

  MemRange(int8_t *const in_offset, uint32_t in_size) : range_offset(in_offset), range_size(in_size) {
    range_end = std::next(range_offset, in_size - 1);
    is_dirty = false;
  }
};
//=====================================================================
// DefaultMemAllocator
// use std::alloc() and std::free()
//=====================================================================
static const uint32_t MEM_BLOCK_SIZE = 1024 * 1024;
class DefaultMemAllocator {
 public:
  explicit DefaultMemAllocator(uint32_t in_count) : block_count_(in_count) {
    // Pre-allocate chunks of memory and insert into list
    uint32_t block_index = 0;
    for (; block_index < block_count_; ++block_index) {
      std::vector<int8_t> mem_block(MEM_BLOCK_SIZE);
      memory_blocks_.push_back(std::move(mem_block));
      free_blocks_.push_back(&memory_blocks_.back());
    }
  }

  int8_t *mem_calloc(uint32_t block_num, uint32_t block_size) {
    return allocate(block_num * block_size);
  }

  void mem_free(const int8_t *const in_addr) {
    if (in_addr != nullptr) {
      deallocate(in_addr);
    } else {
      // Do nothing
    }
  }

  int8_t *allocate(uint32_t alloc_size);

  void deallocate(const int8_t *const in_ptr);

 private:
  uint32_t block_count_;
  std::list<std::vector<int8_t>> memory_blocks_;
  std::unordered_map<const std::vector<int8_t> *, uint32_t> used_sizes_;
  std::list<const std::vector<int8_t> *> free_blocks_;
};

class MemAllocDelegate {
 public:
  int8_t *mem_calloc(uint32_t in_block_num, uint32_t in_block_size, bool in_is_sys_malloc);
  void mem_free(const int8_t *const in_addr);
  static std::shared_ptr<MemAllocDelegate> get_instance();
  /**
   * return which allocator be used to allocated the in_addr
   * @return allocator's name
   */
  const char *get_allocator_name(const int8_t *const in_addr) const;
  void setup_live_direct_memory_mode(uint32_t in_base_addr, uint32_t in_high_addr);

  MemAllocDelegate();
  ~MemAllocDelegate() {
  }

 private:
  MemAllocDelegate(const MemAllocDelegate &) = delete;
  MemAllocDelegate &operator=(const MemAllocDelegate &) = delete;

  std::shared_ptr<DefaultMemAllocator> default_allocator_ptr_;
  std::shared_ptr<MemMapMemAllocator> memmap_allocator_ptr_;

  static std::shared_ptr<MemAllocDelegate> alloc_dele_ptr_;
  static std::mutex delegate_mutex_;
  std::mutex instance_mutex_;
  bool is_live_direct_memory_mode;
  uint32_t allocation_cnt_;
};

}  // namespace MemTest
#endif  // UTILS_MEM_ALLOCATOR_H_
